XNIO, like NIO, is based on the usage of buffers as implemented by the NIO buffer classes in the java.nio package. The NIO documentation defines a java.nio.Buffer as "a linear, finite sequence of elements of a specific primitive type". There are buffer types corresponding to every primitive type; however, as a practical matter, networking software will rarely use a buffer type other than java.nio.ByteBuffer.
Buffers are mutable, meaning that the data in the buffer is subject to alteration, as are the buffer's properties. Buffers are also unsafe for use in multiple threads without some type of external synchronization.
There are three primary attributes in a java.nio.Buffer:
the position, a zero-based mutable integer value representing the point in the buffer from which the next item will be read or written
the limit, a zero-based mutable integer value which is used to mark the end of the buffer's data when reading or the point at which no more data may be added when writing (this value is always greater than or equal to the position)
the capacity, a zero-based fixed integer value which represents the total size of the buffer (this value is always greater than or equal to the limit)
In addition, there is one "virtual" attribute which may be derived from these: the remaining size, which is equal to the difference between the position and the limit.
These properties are used to provide boundaries for data within a buffer; typically a buffer will have a larger capacity than limit (meaning that there is more space in the buffer than there is actual useful data). The position and limit properties allow the application to deal with data that is of a possibly smaller size than the buffer's total capacity.
Buffers can be duplicated using the unsurprisingly-named ByteBuffer.duplicate() method. This causes a new buffer object to be created with the same backing storage as the original buffer, but with independent position and limit values.
Similarly, a buffer can be sliced, meaning a subsection of a buffer's backing storage can be exposed as a new buffer. This can be done using the ByteBuffer.slice() method, which creates a new buffer whose content begins at the current buffer's position, and whose capacity is equal to the remaining space in the original buffer. See the Buffer Utilities section for more useful ways to slice buffers.
In both cases, you must be aware of the fact that the backing content is shared, especially if multiple threads or asynchronous tasks may handle the buffer simultaneously.